﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;

namespace HeliGame
{
    public struct RotatedBBox
    {
        public Vector3 v1, v2, v3, v4;
        public float height;
    }

    class Helicopter
    {
        Model model;
    
        Matrix[] modelTransforms;
        public Texture texture;
        Vector3 position;
        Vector3 speed = new Vector3(0, 0, 0);
        BoundingBox boundingBox;
        float bottom, up;
        bool alive;
        bool onTheGround;

        public bool Alive
        {
            get
            {
                return alive;
            }
            set
            {
                alive = value;
            }
        }

        public Vector3 Position
        {
            get
            {
                return position;
            }
        }

        public float Yaw
        {
            get
            {
                return yaw;
            }
        }
        public float Pitch
        {
            get
            {
                return pitch;
            }
        }
        public float Roll
        {
            get
            {
                return roll;
            }
        }
        public float Bottom
        {
            get
            {
                return bottom;
            }
        }
        public float Up
        {
            get
            {
                return up;
            }
        }

        public RotatedBBox RotatedBoundingBox
        {
            get
            {
                Vector3 min = boundingBox.Min, max = boundingBox.Max;
                RotatedBBox rbbox;
                rbbox.v1 = min;
                rbbox.v2 = new Vector3(max.X, min.Y, min.Z);
                rbbox.v3 = new Vector3(max.X, min.Y, max.Z);
                rbbox.v4 = new Vector3(min.X, min.Y, max.Z);
                rbbox.height = max.Y - min.Y;

                Matrix transformation = Matrix.CreateFromYawPitchRoll(-yaw, 0, 0) * Matrix.CreateTranslation(position);
               
                rbbox.v1 = Vector3.Transform(rbbox.v1, transformation);
                rbbox.v2 = Vector3.Transform(rbbox.v2, transformation);
                rbbox.v3 = Vector3.Transform(rbbox.v3, transformation);
                rbbox.v4 = Vector3.Transform(rbbox.v4, transformation);

                return rbbox;
            }
        }

        
        float liftForce;
        float yaw, pitch, roll;
        float mainRotorAngle = 0, tailRotorAngle = 0;
        public float mainRotorSpeed = 0;

        //--parametry lotu--------------------------
        
        
        const float gravity = -0.26f;
        const float speedFactor = 0.3f;
        const float posChangeFactor = 0.1f; //im mniejszy, tym większa bezwładność helikoptera
        const float throttleFactor = 0.6f; //im większy, tym bardziej czuły drążek gazu
        const float liftFactor = 0.2f;
        const float dropFactor = 0.3f;
        const float rudderFactor = 0.06f; //szybkość obrotów yaw
        const float rollFactor = 0.1f;  //im mniejszy, tym większa bezwładność przechylenia roll
        const float pitchFactor = 0.1f; //im mniejszy, tym większa bezwładność przechylenia pitch
        const float airFrictionFactor = 0.994f; //im mniejszy tym szybsze hamowanie w powietrzu
        const float MAXROLL = MathHelper.PiOver4 / 2.0f; //maksymalne przechylenie roll
        const float MAXPITCH = MathHelper.PiOver4 / 2.0f; //maksymalne przechylenie pitch
        const float mainRotorFactor = 5f; // prędkość obrotu głównego wirnika względem liftForce
        const float mainRotorExpoFactor = 0.02f; //im mniejszy, tym większa bezwładność głównego wirnika
        const float tailGearRatio = 1.6f; //stosunek prędkości obrotowej wirnika ogonowego do głównego
        


        /*
        const float gravity = -0.01f;
        const float speedFactor = 0.3f;
        const float posChangeFactor = 0.1f;
        const float throttleFactor = 0.4f;
        const float liftFactor = 0.01f;
        const float dropFactor = 0.3f;
        const float rudderFactor = 0.05f;
        const float rollFactor = 0.1f;
        const float pitchFactor = 0.1f;
        const float airFrictionFactor = 0.994f;
        const float MAXROLL = MathHelper.PiOver4 / 2.5f;
        const float MAXPITCH = MathHelper.PiOver4 / 2.5f;
        const float mainRotorFactor = 5f;
        const float mainRotorExpoFactor = 0.02f;
        const float tailGearRatio = 2f;
        */
         
        //------------------------------------------

        Vector3 netForce;

        public void Move()
        {
            if (!alive) return;

            speed += netForce * speedFactor;
            position += speed * posChangeFactor;

            speed *= airFrictionFactor;

            if (position.Y + bottom <= 0.001)
            {
                if (speed.Length() > 0.7f) alive = false;
                speed.Y = 0;
                position.Y = -bottom;
                roll = roll * 0.9f;
                pitch = pitch * 0.9f;
                speed.X = speed.X * 0.9f;
                speed.Z = speed.Z * 0.9f;
            }
        }

        public void CalcFlightParams(float throttle, float rudder, float x, float y)
        {
            if (throttle < 0.02) throttle = 0;
            onTheGround = (position.Y + bottom <= 0);
            throttle *= throttleFactor;
            if (throttle > liftForce)
            {
                liftForce += liftFactor * (throttle - liftForce); //zwiększ liftForce
                if (liftForce > throttle) liftForce = throttle;
            }
            else
            {
                liftForce += dropFactor * (throttle - liftForce); //zmniejsz liftForce
                if (liftForce < throttle) liftForce = throttle;
            }


            mainRotorSpeed += (liftForce * mainRotorFactor - mainRotorSpeed) * mainRotorExpoFactor;

            mainRotorAngle += Math.Min(1.0f, mainRotorSpeed);
            if (mainRotorAngle > MathHelper.TwoPi) mainRotorAngle -= MathHelper.TwoPi;
            if (mainRotorAngle < -MathHelper.TwoPi) mainRotorAngle += MathHelper.TwoPi;

            tailRotorAngle = mainRotorAngle * tailGearRatio;

            

            if (!onTheGround)
            {
                float destinationRoll = x * MAXROLL;
                roll += rollFactor * (destinationRoll - roll);
                float destinationPitch = y * MAXPITCH;
                pitch += pitchFactor * (destinationPitch - pitch);

                yaw += rudderFactor * rudder;
            }
            if (yaw > MathHelper.TwoPi) yaw -= MathHelper.TwoPi;
            if (yaw < -MathHelper.TwoPi) yaw += MathHelper.TwoPi;

           

            Vector3 liftVector = new Vector3(0, liftForce, 0);
            liftVector = Vector3.Transform(liftVector, Quaternion.CreateFromYawPitchRoll(-yaw, -pitch, -roll));

            netForce = new Vector3(0, gravity, 0) + liftVector;
        }


        private BoundingBox CalcBoundingBox()
        {
            Vector3 modelMax = new Vector3(float.MinValue, float.MinValue, float.MinValue);
            Vector3 modelMin = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue);

            foreach (ModelMesh mesh in model.Meshes)
            {
                Vector3 meshMax = new Vector3(float.MinValue, float.MinValue, float.MinValue);
                Vector3 meshMin = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue);

                foreach (ModelMeshPart part in mesh.MeshParts)
                {
                    int stride = part.VertexBuffer.VertexDeclaration.VertexStride;

                    Vector3[] vertexData = new Vector3[part.NumVertices];

                    part.VertexBuffer.GetData<Vector3>(part.VertexOffset * stride, vertexData, 
                        0, vertexData.Length, stride);
                    
                    for (int i = 0; i < vertexData.Length; i++)
                    {
                        meshMin = Vector3.Min(meshMin, vertexData[i]);
                        meshMax = Vector3.Max(meshMax, vertexData[i]);
                    }
                }

                meshMin = Vector3.Transform(meshMin, modelTransforms[mesh.ParentBone.Index]);
                meshMax = Vector3.Transform(meshMax, modelTransforms[mesh.ParentBone.Index]);

                modelMin = Vector3.Min(modelMin, meshMin);
                modelMax = Vector3.Max(modelMax, meshMax);
            }

            bottom = modelMin.Y;
            up = modelMax.Z;
            return new BoundingBox(modelMin, modelMax);
        }

        public void Reset()
        {
            position = new Vector3(2.5f, -boundingBox.Min.Y, -3);
            speed = new Vector3(0, 0, 0);
            yaw = pitch = roll = liftForce = mainRotorSpeed = 0;
            alive = onTheGround = true;
        }

        public Helicopter(Model model, Texture texture)
        {
            this.model = model;
            this.texture = texture;
            modelTransforms = new Matrix[model.Bones.Count];
            model.CopyAbsoluteBoneTransformsTo(modelTransforms);

            boundingBox = CalcBoundingBox();
            Reset();
        }

        public void SetEffect(Effect effect)
        {
            foreach (ModelMesh mesh in model.Meshes)
                foreach (ModelMeshPart meshPart in mesh.MeshParts)
                    meshPart.Effect = effect;
        }

        public void Draw(GraphicsDevice device)
        {
            Matrix worldMatrix =  Matrix.CreateFromYawPitchRoll(-yaw, -pitch, -roll) * Matrix.CreateTranslation(position);
            
            foreach (ModelMesh mesh in model.Meshes)
            {
                foreach (Effect currentEffect in mesh.Effects)
                {
                    currentEffect.Parameters["xWorld"].SetValue(modelTransforms[mesh.ParentBone.Index] * worldMatrix);
                    //currentEffect.Parameters["xLightPosition"].SetValue(new Vector3(0, 4.5f, 2));

                    if (mesh.Name.Equals("cylinder2"))
                    {
                       currentEffect.Parameters["xWorld"].SetValue(Matrix.CreateRotationY(mainRotorAngle) * 
                           modelTransforms[mesh.ParentBone.Index] * worldMatrix);
                    }
                    else if (mesh.Name.Equals("cylinder4"))
                    {
                        Vector3 tempTransl = new Vector3(0, -0.016f, 0.033f);

                        currentEffect.Parameters["xWorld"].SetValue(Matrix.CreateTranslation(tempTransl) *
                           Matrix.CreateRotationX(tailRotorAngle) * Matrix.CreateTranslation(-tempTransl) *
                           modelTransforms[mesh.ParentBone.Index] * worldMatrix);
                    }
                    else
                    {
                       currentEffect.Parameters["xWorld"].SetValue(modelTransforms[mesh.ParentBone.Index] * worldMatrix);
                    }
    
                }


              
                mesh.Draw();
            }
        }       

    
        
        /*public BoundingBox GetBoundingBox()
        {
            Vector3 min = boundingBox.Min, max = boundingBox.Max;
            Matrix transformation = Matrix.CreateFromYawPitchRoll(-yaw, 0, 0) * Matrix.CreateTranslation(position);
            min = Vector3.Transform(min, transformation);
            max = Vector3.Transform(max, transformation);
            BoundingBox bb = new BoundingBox(min, max);
            return bb;

        }*/
        
        
        public BoundingBox GetBoundingBox()
        {

            Vector3[] obb = new Vector3[8];
            boundingBox.GetCorners(obb);
            Matrix transformation = Matrix.CreateFromYawPitchRoll(-yaw, 0, 0) * Matrix.CreateTranslation(position);
            Vector3.Transform(obb, ref transformation, obb);
            BoundingBox bb = BoundingBox.CreateFromPoints(obb);
            return bb;
        }



      
    }
}
